﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Linq.Expressions;

namespace SimpleFormulaParser
{
    interface IFormulaExp
    {
        Expression ParseString(string formula);
        ParameterExpression Prm
        {set; get; }
    }

    class FormulaExp<CT, PT>: IFormulaExp                       
    {
        private Formula<Expression> FormulaBuilder;

        private List<PropertyInfo> Properties
        { set; get; }

        PT PTDefaultValue
        { set; get; }
    
        public FormulaExp()
        {
            if ((typeof(PT) == typeof(int)) || (typeof(PT) == typeof(float)) || (typeof(PT) == typeof(double)))
                PTDefaultValue = (PT)Convert.ChangeType(0, typeof(PT));
            Properties = (from p in typeof(CT).GetProperties() where p.PropertyType == typeof(PT) select p).ToList<PropertyInfo>();
            FormulaBuilder = new Formula<Expression>();
            FormulaBuilder.Zero = Expression.Constant(PTDefaultValue, typeof(PT));
         //   FormulaBuilter = new Formula<PT>();
            Prm = Expression.Parameter(typeof(CT), "CT");
        }

        public ParameterExpression Prm
        {set; get; }

        Expression StringToExp(string value)
        {
            PT ptvaule = PTDefaultValue;
            try
            {
                ptvaule = (PT)Convert.ChangeType(value, typeof(PT));                 
            }
            catch (Exception e)
            {
                if (Properties.Exists(p => p.Name == value))
                    return Expression.Property(Prm, value);
                else throw e;
            }
            return Expression.Constant(ptvaule, typeof(PT));
        }


        private Expression binaryOperationExp(string op, Expression valueLeft, Expression valueRight)
        {
            switch (op)
            {
                case "Add":
                    {
                        Expression exp;
                        exp = Expression.Add(valueLeft, valueRight);
                        return exp;
                    }
                case "Sub":
                    {
                        Expression exp;
                        exp = Expression.Subtract(valueLeft, valueRight);
                        return exp;

                    }
                case "Mul":
                    {
                        Expression exp;
                        exp = Expression.Multiply(valueLeft, valueRight);
                        return exp;
                    }
                case "Div":
                    {
                        Expression exp;
                        exp = Expression.Divide(valueLeft, valueRight);
                        return exp;
                    }
                default:
                    {
                        return Expression.Constant(0, typeof(int));
                    }
            }

        }

        public Expression ParseString(string formula)
        {            
            return FormulaBuilder.Calculation(formula, StringToExp, binaryOperationExp);
          //  Expression<Func<T, int>> func = Expression.Lambda<Func<T, int>>(result0, new ParameterExpression[] { prm });
        }



        public Expression<Func<CT, PT>> BuildFunction(string formula)
        {
            return Expression.Lambda<Func<CT, PT>>(FormulaBuilder.Calculation(formula, StringToExp, binaryOperationExp), 
                new ParameterExpression[] { Prm });
        }
    }


    class FormulaExpFactory
    {

        static List<Type> _allowedTypes = new List<Type>() { typeof(int), typeof(long), typeof(Single), typeof(double), typeof(float) };

        public static IFormulaExp CreateFormulaExp<CT>(Type PT)
        {
            int idx = _allowedTypes.FindIndex(t => PT == t);

            switch (idx)
            {
                case 0:
                    {
                        return new FormulaExp<CT, int>();
                    }

                case 1:
                    {
                        return new FormulaExp<CT, long>();
                    }

                case 2:
                    {
                        return new FormulaExp<CT, Single>();
                    }

                case 3:
                    {
                        return new FormulaExp<CT, double>();
                    }

                case 4:
                    {
                        return new FormulaExp<CT, double>();
                    }
                default:
                    {
                        return new FormulaExp<CT, double>();
                    }
            }

        }
    }
}
